/**
 * Copyright (c) 2013-2022, Alibaba Group Holding Limited;
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * </p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aliyun.polardbx.binlog.canal.binlog.event;

import com.aliyun.polardbx.binlog.canal.binlog.LogBuffer;
import com.aliyun.polardbx.binlog.canal.binlog.LogEvent;

/**
 * The Common-Header, documented in the table @ref Table_common_header "below", always has the same form and length
 * within one version of MySQL. Each event type specifies a format and length of the Post-Header. The length of the
 * Common-Header is the same for all events of the same type. The Body may be of different format and length even for
 * different events of the same type. The binary formats of Post-Header and Body are documented separately in each
 * subclass. The binary format of Common-Header is as follows.
 * <table>
 * <caption>Common-Header</caption>
 * <tr>
 * <th>Name</th>
 * <th>Format</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>timestamp</td>
 * <td>4 byte unsigned integer</td>
 * <td>The time when the query started, in seconds since 1970.</td>
 * </tr>
 * <tr>
 * <td>type</td>
 * <td>1 byte enumeration</td>
 * <td>See enum #Log_event_type.</td>
 * </tr>
 * <tr>
 * <td>server_id</td>
 * <td>4 byte unsigned integer</td>
 * <td>Server ID of the server that created the event.</td>
 * </tr>
 * <tr>
 * <td>total_size</td>
 * <td>4 byte unsigned integer</td>
 * <td>The total size of this event, in bytes. In other words, this is the sum of the sizes of Common-Header,
 * Post-Header, and Body.</td>
 * </tr>
 * <tr>
 * <td>master_position</td>
 * <td>4 byte unsigned integer</td>
 * <td>The position of the next event in the master binary log, in bytes from the beginning of the file. In a binlog
 * that is not a relay log, this is just the position of the next event, in bytes from the beginning of the file. In a
 * relay log, this is the position of the next event in the master's binlog.</td>
 * </tr>
 * <tr>
 * <td>flags</td>
 * <td>2 byte bitfield</td>
 * <td>See Log_event::flags.</td>
 * </tr>
 * </table>
 * Summing up the numbers above, we see that the total size of the common header is 19 bytes.
 *
 * @author Changyuan.lh
 * @version 1.0
 * @see mysql-5.1.60/sql/log_event.cc
 */
public final class LogHeader {

    protected final int type;

    /**
     * The offset in the log where this event originally appeared (it is preserved in relay logs, making SHOW SLAVE
     * STATUS able to print coordinates of the event in the master's binlog). Note: when a transaction is written by the
     * master to its binlog (wrapped in BEGIN/COMMIT) the log_pos of all the queries it contains is the one of the BEGIN
     * (this way, when one does SHOW SLAVE STATUS it sees the offset of the BEGIN, which is logical as rollback may
     * occur), except the COMMIT query which has its real offset.
     */
    protected long logPos;

    /**
     * Timestamp on the master(for debugging and replication of NOW()/TIMESTAMP). It is important for queries and LOAD
     * DATA INFILE. This is set at the event's creation time, except for Query and Load (et al.) events where this is
     * set at the query's execution time, which guarantees good replication (otherwise, we could have a query and its
     * event with different timestamps).
     */
    protected long when;

    /**
     * Number of bytes written by write() function
     */
    protected int eventLen;

    /**
     * The master's server id (is preserved in the relay log; used to prevent from infinite loops in circular
     * replication).
     */
    protected long serverId;

    /**
     * Some 16 flags. See the definitions above for LOG_EVENT_TIME_F, LOG_EVENT_FORCED_ROTATE_F,
     * LOG_EVENT_THREAD_SPECIFIC_F, and LOG_EVENT_SUPPRESS_USE_F for notes.
     */
    protected int flags;

    /**
     * The value is set by caller of FD constructor and Log_event::write_header() for the rest. In the FD case it's
     * propagated into the last byte of post_header_len[] at FD::write(). On the slave side the value is assigned from
     * post_header_len[last] of the last seen FD event.
     */
    protected int checksumAlg;
    /**
     * Placeholder for event checksum while writing to binlog.
     */
    protected long crc; // ha_checksum

    protected int startPos;

    protected byte[] dataBuffer;

    protected FormatDescriptionLogEvent formatDescriptionLogEvent;

    /* for Start_event_v3 */
    public LogHeader(final int type) {
        this.type = type;
    }

    public LogHeader(LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent) {
        formatDescriptionLogEvent = descriptionEvent;
        startPos = buffer.position();
        when = buffer.getUint32();
        type = buffer.getUint8(); // LogEvent.EVENT_TYPE_OFFSET;
        serverId = buffer.getUint32(); // LogEvent.SERVER_ID_OFFSET;
        eventLen = (int) buffer.getUint32(); // LogEvent.EVENT_LEN_OFFSET;
        if (descriptionEvent.binlogVersion == 1) {
            logPos = 0;
            flags = 0;
            return;
        }

        /* 4.0 or newer */
        logPos = buffer.getUint32(); // LogEvent.LOG_POS_OFFSET
        /*
         * If the log is 4.0 (so here it can only be a 4.0 relay log read by the SQL thread or a 4.0 master binlog read
         * by the I/O thread), log_pos is the beginning of the event: we transform it into the end of the event, which
         * is more useful. But how do you know that the log is 4.0: you know it if description_event is version 3 *and*
         * you are not reading a Format_desc (remember that mysqlbinlog starts by assuming that 5.0 logs are in 4.0
         * format, until it finds a Format_desc).
         */
        if (descriptionEvent.binlogVersion == 3 && type < LogEvent.FORMAT_DESCRIPTION_EVENT && logPos != 0) {
            /*
             * If log_pos=0, don't change it. log_pos==0 is a marker to mean "don't change rli->group_master_log_pos"
             * (see inc_group_relay_log_pos()). As it is unreal log_pos, adding the event len's is nonsense. For
             * example, a fake Rotate event should not have its log_pos (which is 0) changed or it will modify
             * Exec_master_log_pos in SHOW SLAVE STATUS, displaying a nonsense value of (a non-zero offset which does
             * not exist in the master's binlog, so which will cause problems if the user uses this value in CHANGE
             * MASTER).
             */
            logPos += eventLen; /* purecov: inspected */
        }

        flags = buffer.getUint16(); // LogEvent.FLAGS_OFFSET
        if ((type == LogEvent.FORMAT_DESCRIPTION_EVENT) || (type == LogEvent.ROTATE_EVENT)) {
            /*
             * These events always have a header which stops here (i.e. their header is FROZEN).
             */
            /*
             * Initialization to zero of all other Log_event members as they're not specified. Currently there are no
             * such members; in the future there will be an event UID (but Format_description and Rotate don't need this
             * UID, as they are not propagated through --log-slave-updates (remember the UID is used to not play a query
             * twice when you have two masters which are slaves of a 3rd master). Then we are done.
             */

            if (type == LogEvent.FORMAT_DESCRIPTION_EVENT) {
                int commonHeaderLen = buffer.getUint8(FormatDescriptionLogEvent.LOG_EVENT_MINIMAL_HEADER_LEN
                    + FormatDescriptionLogEvent.ST_COMMON_HEADER_LEN_OFFSET);
                buffer.position(commonHeaderLen + FormatDescriptionLogEvent.ST_SERVER_VER_OFFSET);
                String serverVersion =
                    buffer.getFixString(FormatDescriptionLogEvent.ST_SERVER_VER_LEN); // ST_SERVER_VER_OFFSET
                int versionSplit[] = new int[] {0, 0, 0};
                FormatDescriptionLogEvent.doServerVersionSplit(serverVersion, versionSplit);
                checksumAlg = LogEvent.BINLOG_CHECKSUM_ALG_UNDEF;
                if (FormatDescriptionLogEvent.versionProduct(versionSplit)
                    >= FormatDescriptionLogEvent.checksumVersionProduct) {
                    buffer.position(eventLen - LogEvent.BINLOG_CHECKSUM_LEN - LogEvent.BINLOG_CHECKSUM_ALG_DESC_LEN);
                    checksumAlg = buffer.getUint8();
                }

                processCheckSum(buffer);
            }
            return;
        }

        /*
         * CRC verification by SQL and Show-Binlog-Events master side. The caller has to provide
         * @description_event->checksum_alg to be the last seen FD's (A) descriptor. If event is FD the descriptor is in
         * it. Notice, FD of the binlog can be only in one instance and therefore Show-Binlog-Events executing master
         * side thread needs just to know the only FD's (A) value - whereas RL can contain more. In the RL case, the alg
         * is kept in FD_e (@description_event) which is reset to the newer read-out event after its execution with
         * possibly new alg descriptor. Therefore in a typical sequence of RL: {FD_s^0, FD_m, E_m^1} E_m^1 will be
         * verified with (A) of FD_m. See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg docs lines
         * (log.h). Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF.
         */
        checksumAlg = descriptionEvent.getHeader().checksumAlg; // fetch
        // checksum alg
//        processCheckSum(buffer);
        /* otherwise, go on with reading the header from buf (nothing now) */

    }

    public void initDataBuffer(LogBuffer buffer) {
        dataBuffer = buffer.getData(startPos, eventLen);
    }

    /**
     * The different types of log events.
     */
    public final int getType() {
        return type;
    }

    /**
     * The position of the next event in the master binary log, in bytes from the beginning of the file. In a binlog
     * that is not a relay log, this is just the position of the next event, in bytes from the beginning of the file. In
     * a relay log, this is the position of the next event in the master's binlog.
     */
    public final long getLogPos() {
        return logPos;
    }

    /**
     * The total size of this event, in bytes. In other words, this is the sum of the sizes of Common-Header,
     * Post-Header, and Body.
     */
    public final int getEventLen() {
        return eventLen;
    }

    /**
     * The time when the query started, in seconds since 1970.
     */
    public final long getWhen() {
        return when;
    }

    /**
     * Server ID of the server that created the event.
     */
    public final long getServerId() {
        return serverId;
    }

    /**
     * Server ID of the server that created the event.
     */
    public final void setServerId(long serverId) {
        this.serverId = serverId;
    }

    /**
     * Some 16 flags. See the definitions above for LOG_EVENT_TIME_F, LOG_EVENT_FORCED_ROTATE_F,
     * LOG_EVENT_THREAD_SPECIFIC_F, and LOG_EVENT_SUPPRESS_USE_F for notes.
     */
    public final int getFlags() {
        return flags;
    }

    public long getCrc() {
        return crc;
    }

    public int getChecksumAlg() {
        return checksumAlg;
    }

    public void processCheckSum(LogBuffer buffer) {
        if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
            crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
        }
    }

    public long getStartPos() {
        return startPos;
    }

    public FormatDescriptionLogEvent getFormatDescriptionLogEvent() {
        return formatDescriptionLogEvent;
    }

    public byte[] getDataBuffer() {
        return dataBuffer;
    }
}
